package com.gitee.wsl.mathematics.vector.vec2


import com.gitee.wsl.ext.array.FloatArray
import com.gitee.wsl.io.dataproxy.DataProxy
import com.gitee.wsl.io.dataproxy.asProxy
import com.gitee.wsl.mathematics.coordinate.d2.MutableCoordinate2F
import com.gitee.wsl.mathematics.vector.vec3.MutableVec3f
import com.gitee.wsl.mathematics.vector.vec4.MutableVec4f

import com.gitee.wsl.unit.angle.AngleUnit
import com.gitee.wsl.unit.angle.ext.cos
import com.gitee.wsl.unit.angle.ext.plus
import com.gitee.wsl.unit.angle.ext.sin
import com.gitee.wsl.unit.angle.toRadians
import kotlin.math.*
import kotlin.random.Random

/**
 * A class representing a two-dimensional / planar vector.
 */
class MutableVec2f(override val proxy: DataProxy<Float>):
    Vec2f(proxy=proxy),MutableCoordinate2F<Vec2f> {
    constructor(x: Float=0.0f, y: Float=0.0f):this(FloatArray(x,y).asProxy())
    constructor(a: Number) : this(a.toFloat(), a.toFloat())
    constructor(x: Number, y: Number) : this(x.toFloat(), y.toFloat())
    constructor(base: Vector2F<*>) : this(base.x, base.y)
    constructor(base: Vector2<*,*>): this(base.x, base.y)

    override val self
        get() = this

    override val vec2 get() = this
    override val vec3 get() = MutableVec3f(x, y, 0.0)
    override val vec4 get() = MutableVec4f(x, y, 0.0, 0.0)

    override fun create(x: Float, y: Float) = MutableVec2f(x,y)

    companion object {
        val right get() = MutableVec2f(1.0, 0.0)
        val left get() = MutableVec2f(-1.0, 0.0)
        val up get() = MutableVec2f(0.0, 1.0)
        val down get() = MutableVec2f(0.0, -1.0)
        val one get() = MutableVec2f(1.0, 1.0)
        val zero get() = MutableVec2f(0.0, 0.0)
        val negativeInfinity get() = MutableVec2f(Float.NEGATIVE_INFINITY)
        val positiveInfinity get() = MutableVec2f(Float.POSITIVE_INFINITY)
    }


    /**Reflects the vector from [normal].*/
    /*override infix fun reflectFrom(normal: Vec2f): MutableVec2f {
        val norm = normal.normalized
        val factor = -2.0 * this dot norm
        return MutableVec2f(factor * norm + this)
    }*/

    /**
     * Rotates the vector by [angle] radians
     * @param angle The angle in **radians**.
     * @return The vector rotated by [angle] radians.
     */
    /*override infix fun rotatedBy(angle: Float): MutableVec2f {
        val sin = sin(angle)
        val cos = cos(angle)
        return MutableVec2f((cos * x) - (sin * y), (sin * x) + (cos * y))
    }*/

    override fun equals(other: Any?) = other is MutableVec2f && x == other.x && y == other.y

    override fun hashCode(): Int {
        var result = super.hashCode()
        result = 31 * result + x.hashCode()
        result = 31 * result + y.hashCode()
        return result
    }

}

/**
 * Parses a 2D Coordinate from a string.
 * @param point String
 * @return 2D Coordinate
 */
fun MutableVec2f.Companion.fromString(point: String): MutableVec2f {
    val point0 = point.trim { it <= ' ' }.replace("[\\[\\]\\s]".toRegex(), "")
    val parts = point0.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
    if (parts.size != 2) throw IllegalArgumentException("Invalid Coordinate Length")

    val x = parts[0].toFloat(); val y = parts[1].toFloat()
    return MutableVec2f(x, y)
}


/**
 * Creates a vector from an [angle] and [length]
 *
 * Given any angle in radians and length/magnitude of a vector, it'll create a vector with
 * it's x-component and y-component
 *
 * @param angle Angle in radians
 * @param length length in float
 *
 * @return [Vector2D] vector for given angle and with given magnitude
 */
fun MutableVec2f.Companion.fromAnAngle(angle: Float, length: Float = 1f): MutableVec2f {
    return MutableVec2f(length * cos(angle), length * sin(angle))
}

/**
 * Creates a random vector (unit vector)
 * Can be used to create a random unit vector along any direction
 * @return a random unit [Vector2D]
 */
fun MutableVec2f.Companion.randomVector(): MutableVec2f {
    return fromAnAngle((Random.nextFloat() * PI * 2).toFloat())
}

/**
 * Sets [this] vector as [other] vector
 */
/*fun MutableVec2f.set(other: MutableVec2f): MutableVec2f {
    this.x = other.x
    this.y = other.y
    return this
}*/

/**
 * To add a vector to [this] vector and multiplies the [other] vector with [scalar]
 */
fun MutableVec2f.addWithScalarMultiply(other: MutableVec2f, scalar: Float): MutableVec2f {
    this.x += other.x * scalar
    this.y += other.y * scalar
    return this
}

/**
 * To divide [this] vector by [x] and [y] values
 */
fun MutableVec2f.div(x: Float, y: Float): MutableVec2f {
    this.x /= x
    this.y /= y
    return this
}

fun MutableVec2f.scalarMultiply(scalar: Float): MutableVec2f {
    this.x *= scalar
    this.y *= scalar
    return this
}

/**
 * Limit the magnitude of this vector to the value used for the <b>max</b>
 * parameter.
 * <code>
 * val v = Vector2D(10, 20);
 * // v has components [10.0, 20.0]
 * v.limit(5);
 * // v's components are set to
 * // [2.2271771, 4.4543543 ]
 * </code>
 */
fun MutableVec2f.limit(max: Float): MutableVec2f {
    val magSq = this.squaredLength
    if (magSq > max * max) {
        val norm = sqrt(magSq)
        this.div(norm, norm).scalarMultiply(max)
    }
    return this
}

/**
 * Linear interpolate the vector to another vector
 * @param x the x component
 * @param y the y component
 * @param amt the amount of interpolation; some value between 0.0
 *                     (old vector) and 1.0 (new vector). 0.9 is very near
 *                      the new vector. 0.5 is halfway in between.
 */
fun MutableVec2f.lerp(x: Float, y: Float, amt: Float): MutableVec2f {
    this.x += (x - this.x) * amt
    this.y += (y - this.y) * amt
    return this
}

/**
 * Calculate the angle of rotation for this vector(only 2D vectors).
 */
fun MutableVec2f.heading(): AngleUnit {
    return atan2(this.y, this.x).toRadians()
}

/**
 * Rotate the vector to a specific angle, magnitude remains the same
 * @param angle - Angle in radians
 */
fun MutableVec2f.setHeading(angle: Float): MutableVec2f {
    val mag = this.length
    this.x = mag * cos(angle)
    this.y = mag * sin(angle)
    return this
}

/**
 * Rotates [this] vector by given [angle]
 */
fun MutableVec2f.rotate(angle: AngleUnit): MutableVec2f {
    val newHeading = (this.heading() + angle)
    val mag = this.length
    this.x = (newHeading.cos() * mag).toFloat()
    this.y = (newHeading.sin() * mag).toFloat()
    return this
}